/*******************************************************************************
 * This file is part of blueprint4j www.blueprint4j.org.
 * 
 *     blueprint4j is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     blueprint4j is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with blueprint4j. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package blueprint4j.utils;

import java.util.Vector;
import java.util.*;
import java.text.*;

public class ThreadSchedule extends Thread {
    
    public static final boolean DEBUG = false;
    public static final int SHUTDOWN_FIRST= 0,SHUTDOWN_SECOND = 1,SHUTDOWN_LAST = 2;
    
    public static int HOURLY= 0,DAILY= 1,WEEKLY =2,MONTHLY=3,YEARLY=4;
    private static SimpleDateFormat day_format = new SimpleDateFormat("DDD"),
            hour_format = new SimpleDateFormat("HH"),
            week_format = new SimpleDateFormat("ww"),
            month_format = new SimpleDateFormat("MM"),
            year_format = new SimpleDateFormat("yyyy"),
            time_format = new SimpleDateFormat("HHmmss");
    
    
    private static boolean alive = true;
    private static ThreadSchedule active_schedule = new  ThreadSchedule();
    
    private static Vector threads = new Vector();
    
    static {
        // ADD SHUTDOWN HOOK
        Runtime.getRuntime().addShutdownHook(
                new Thread() {
            public void run(){
                ThreadSchedule.haltBlocking();
            }
        }
        );
        
        // ADD A CLEANUP THREAD
        new Thread() {
            public void run() {
                for (;alive;) {
                    for (int i = 0;i < threads.size();i++) {
                        if (!((ThreadRun)threads.get(i)).stillActive()) {
                            threads.remove(i--);
                        }
                    }
                    try {Thread.sleep(500);} catch (InterruptedException ie) {}
                }
            }
        }.start();
    }
    
    private  ThreadSchedule() {}
    
    public static void add(ThreadScheduable thread) {
        add(thread,Thread.NORM_PRIORITY,SHUTDOWN_FIRST);
    }
    
    public static void add(ThreadScheduable thread,int priority) {
        add(thread,priority,SHUTDOWN_FIRST);
    }
    
    public static void add(ThreadScheduable thread,int priority,int shutdown) {
        ThreadRun t = new ThreadRun(thread,shutdown,threads);
        threads.add(t);
        t.setPriority(priority);
        t.start();
    }
    
    /**
     * Schedule this to run for a certain type and a certain frequency
     * 0 HOURLY 1 DAILY 2 WEEKLY 3 MONTHLY 4 YEARLY
     * Exec Immediately - Should execute immediately
     */
    public static void add(ThreadScheduable thread,int frequency,Date runtime,Date lastrun,boolean exec_immediately,int priority) {
        SchedularWrapper sw = new SchedularWrapper(frequency,runtime,thread,exec_immediately,lastrun);
        ThreadRun t = new ThreadRun(sw,SHUTDOWN_FIRST,threads);
        threads.add(t);
        t.setPriority(priority);
        t.start();
    }
    
    public static void add(ThreadScheduable thread,int frequency,Date runtime,boolean exec_immediately,int priority) {
        add(thread,frequency,runtime,null,exec_immediately,priority);
    }
    
    /**
     * Sends the kill signal to all ThreadScheduable
     */
    public static void halt(ThreadScheduable ts,int shutdown) {
        alive= false;
        for (int i = threads.size()-1;i >= 0;i--) {
            if (((ThreadRun)threads.get(i)).shutdown == shutdown) {
                if (((ThreadRun)threads.get(i)).getThreadScheduable() != ts) {
                    ((ThreadRun) threads.get(i)).halt();
                }
            }
        }
    }
    
    public static void halt(int shutdown) {
        halt(null,shutdown);
    }
    
    public static boolean isStillAlive() {
        return alive;
    }
    
    private static boolean stillThreadsActive(ThreadScheduable ts,int shutdown) {
        boolean active = false;
        for (int i = 0;i < threads.size();i++) {
            if (((ThreadRun)threads.get(i)).shutdown == shutdown) {
                if (((ThreadRun)threads.get(i)).getThreadScheduable()!= ts && ((ThreadRun) threads.get(i)).stillActive()) {
                    Log.dev.out(((ThreadRun) threads.get(i)).getThreadScheduable().getClass() + " is still active");
                    active = true;
                }
            }
        }
        return active;
    }
    
    private static boolean stillThreadsActive(int shutdown) {
        return stillThreadsActive(null,shutdown);
    }
    
    public static void haltBlocking(ThreadScheduable ts,int shutdown) {
        halt(shutdown);
        for (long time = 0;stillThreadsActive(ts,shutdown) && time < 10000;time+=500) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {}
        }
    }
    
    public static void haltBlocking(ThreadScheduable ts) {
        haltBlocking(ts,SHUTDOWN_FIRST);
        haltBlocking(ts,SHUTDOWN_SECOND);
        haltBlocking(ts,SHUTDOWN_LAST);
    }
    
    public static void haltBlocking() {
        haltBlocking(null,SHUTDOWN_FIRST);
        haltBlocking(null,SHUTDOWN_SECOND);
        haltBlocking(null,SHUTDOWN_LAST);
    }
    
    public static void sleep(long time) {
        for (;isStillAlive() && time > 0;time-=500) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {}
        }
    }
    
    public static class SchedularWrapper implements ThreadScheduable {
        
        private int frequency = 0;
        private Date runtime = null,lastexecuted = null;
        private ThreadScheduable thread = null;
        
        public SchedularWrapper(int frequency,Date runtime,ThreadScheduable thread,boolean exec_immediately,Date lastexecuted) {
            //Log.trace.out("THREAD SCHEDULAR","ADDED NEW THREAD ["+thread.getClass().getName()+"]");
            this.frequency = frequency;
            this.runtime = runtime;
            this.thread = thread;
            this.lastexecuted = lastexecuted;
            if (!exec_immediately && lastexecuted == null){
                this.lastexecuted = new Date();
            }
        }
        
        public boolean keepAlive() {return thread.keepAlive();}
        
        public int sleepTime() {return 1000;}
        
        public boolean shouldExecute(int frequency,Date runtime,Date lastrun) {
            if (!(new Integer(time_format.format(new Date())).intValue() > new Integer(time_format.format(runtime)).intValue())) {
                return false;
            }
            if (HOURLY == frequency) {
                return !hour_format.format(lastrun).equals(hour_format.format(new Date()));
            }
            if (DAILY == frequency) {
                return !day_format.format(lastrun).equals(day_format.format(new Date()));
            }
            if (WEEKLY == frequency) {
                return !week_format.format(lastrun).equals(week_format.format(new Date()));
            }
            if (MONTHLY == frequency) {
                return !month_format.format(lastrun).equals(month_format.format(new Date()));
            }
            if (YEARLY == frequency) {
                return !year_format.format(lastrun).equals(year_format.format(new Date()));
            }
            return false;
        }
        
        
        public void process()
        throws Throwable {
            // SHOULD EXECUTE
            if (lastexecuted == null || shouldExecute(frequency,runtime,lastexecuted)) {
                lastexecuted = new Date();
                if (DEBUG) {
                    Log.trace.out("THREAD SCHEDULAR","PROCESS "+ thread.getClass().getName());
                }
                thread.process();
            }
        }
        
        public void close()
        throws Throwable {
            thread.close();
        }
    }
}

class ThreadRun extends Thread {
    private boolean go = true;
    private boolean is_done = false;
    private ThreadScheduable ts = null;
    public int shutdown =ThreadSchedule.SHUTDOWN_FIRST;
    private Vector threads = null;
    
    public ThreadRun(ThreadScheduable ts,int shutdown,Vector threads) {
        this.ts = ts;
        this.shutdown = shutdown;
        this.threads = threads;
    }
    
    public void halt() {
        go = false;
    }
    
    public boolean stillActive() {
        return !is_done;
    }
    
    public ThreadScheduable getThreadScheduable() {
        return ts;
    }
    
    
    private void sleepWhileActive(long time) {
        try {
            for (long slept = 0;go && ts.keepAlive() && slept < time;slept+=50)
                Thread.sleep(50);
        } catch (Throwable exception) {
            exception.printStackTrace();
            Log.debug.out(exception);
        }
    }
    
    public void run() {
        try {
            ts.process();
        } catch (Throwable exception) {
            //go = false;
            exception.printStackTrace();
            Log.debug.out(exception);
        }
        while (go && ts.keepAlive()) {
            try {
                sleepWhileActive(ts.sleepTime());
                if (go && ts.keepAlive()) {
                    if (ThreadSchedule.DEBUG && !(ts instanceof ThreadSchedule.SchedularWrapper)) {
                        Log.trace.out("THREAD SCHEDULAR","PROCESS "+ ts.getClass().getName());
                    }
                    ts.process();
                }
            } catch (Throwable exception) {
                Log.debug.out(exception);
                sleepWhileActive(ts.sleepTime());
            }
        }
        try {
            ts.close();
        } catch (Throwable exception) {
            Log.debug.out(exception);
        }
        // REMOVE YOURSELF FROM VECTOR
        threads.remove(this);
        is_done = true;
    }
}
